<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<div id="app">
		<input type="number" v-model="number">
		<button type='button' v-click='increment'>增加</button>
		<h3 v-bind="number"></h3>
	</div>
	<script>
		function myVue (options) {                     // 定义构造函数myVue
			this._init(options)
		}
		myVue.prototype._init = function (options) {           // 原型链上扩展_init方法
			this.$options = options;                           // 扩展$options属性，值是new myVue()的参数
			this.$el = document.querySelector(options.el);     // 扩展$el,获取原生dom
			this.$data = options.data;                         // 初始化的data属性值
			this.$methods = options.methods;

			this._binding = {};                                // _binding保存着model与view的映射关系，当model改变时，我们会触发其中的指令类更新，保证view也能实时更新
			this._obverse(this.$data);                         // _obverse 函数，对data进行处理，重写data的set和get函数
			this._complie(this.$el);
		}
		myVue.prototype._obverse = function (obj) {
			var value;
			for (key in obj) {
				// hasOwnProperty判断一个对象是否有你给出名称的属性或对象。无法检查该对象的原型链中是否具有该属性
				if (obj.hasOwnProperty(key)) {                 // 这里正好可以排除原型链上的属性。
					this._binding[key] = {
						_directives: []
					}
					value = obj[key];   
					if (typeof value === 'object') {           // 如果值是对象，那么递归查找
						this._obverse(value)
					}
					var binding = this._binding[key]
					Object.defineProperty(this.$data, key, {   // 参数一: 需要被操作的目标对象  参数二: 目标对象需要定义或修改的属性的名称  参数三: 将被定义或修改的属性的描述符
						configurable: true,                    // 表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true
						enumerable: true,                      // 表示该属性是否可枚举，即是否通过for-in循环或Object.keys()返回属性，如果直接使用字面量定义对象，默认值为true
						get: function () {
							return value
						},
						set: function (newValue) {
							if (value !== newValue) {
								value = newValue
								binding._directives.forEach((item) => {
									item.update()
								})
							}
						}
					})
				}
			}
		}
		myVue.prototype._complie = function (root) {
			var _this = this;
			var nodes = root.children;
			for (var i = 0; i < nodes.length; i++) {
				var node = nodes[i];
				if (node.children.length) {
					this._complie(node)
				}
				if (node.hasAttribute('v-click')) {
					node.onclick = (function () {
						var attrVal = nodes[i].getAttribute('v-click');
						return _this.$methods[attrVal].bind(_this.$data)                            // 改变this上下文，这样methods方法里面的this就是指向的Vue对象下面的data对象了
					})()
				}
				if (node.hasAttribute('v-model') && node.tagName == 'INPUT' || node.tagName == 'TEXTAREA') {
					node.addEventListener('input', (function(key){
						var attrVal = nodes[i].getAttribute('v-model');
						_this._binding[attrVal]._directives.push(new Watcher('input', node, _this, attrVal, 'value'))
						return function () {
							_this.$data[attrVal] = nodes[key].value
						}
					})(i))
				}
				if (node.hasAttribute('v-bind')) {
					var attrVal = nodes[i].getAttribute('v-bind');
					_this._binding[attrVal]._directives.push(new Watcher('text', node, _this, attrVal, 'innerHTML'))
				}
			}
		}

		// 写一个指令类Watcher，用来绑定更新函数，实现对DOM元素的更新
	    function Watcher (name, el, vm, exp, attr) {
	    	this.name = name;           // 指令名称，例如文本节点，该值设为"text"
	    	this.el = el;               // 指令对应的DOM元素
	    	this.vm = vm;               // 指令所属myVue实例
	    	this.exp = exp;             // 指令对应的值，本例如"number"
	    	this.attr = attr;           // 绑定的属性值，本例为"innerHTML"
	    	this.update()
	    }
	    Watcher.prototype.update = function () {
	    	this.el[this.attr] = this.vm.$data[this.exp]     // 比如 H3.innerHTML = this.data.number; 当number改变时，会触发这个update函数，保证对应的DOM内容进行了更新。
	    }

	    window.onload = function () {
	    	var app = new myVue({
	    		el: '#app',
	    		data: {
	    			number: ''
	    		},
	    		methods: {
	    			increment: function () {
	    				this.number++
	    			}
	    		}
	    	})
	    }
	</script>
</body>
</html>